/*
 * Copyright (C) 2012 Zhenya Leonov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jpal.base;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.InvalidPropertiesFormatException;
import java.util.Properties;

import com.google.common.io.ByteSink;
import com.google.common.io.ByteSource;
import com.google.common.io.Closer;
import com.google.common.io.Files;

/**
 * Static utility methods for working with {@link Properties}.
 * 
 * @author Zhenya Leonov
 */
final public class MoreProperties {

	private MoreProperties() {
	}

	/**
	 * Returns a new Java {@code Properties} object loaded from the specified
	 * file.
	 * 
	 * @param file
	 *            the specified file
	 * @return a new Java {@code Properties} object loaded from the specified
	 *         file
	 * @throws IllegalArgumentException
	 *             if the input stream contains a malformed Unicode escape
	 *             sequence.
	 * @throws IOException
	 *             if an I/O error occurs
	 * @see Properties#load(InputStream)
	 * @see Properties#load(java.io.Reader)
	 */
	public static Properties load(final File file) throws IOException {
		checkNotNull(file);
		return load(null, file);
	}

	/**
	 * Returns a new Java {@code Properties} object loaded from the specified
	 * file, with optional defaults.
	 * 
	 * @param defaults
	 *            the default properties or {@code null}
	 * @param file
	 *            the specified file
	 * @return a new Java {@code Properties} object loaded from the specified
	 *         file, with optional defaults
	 * @throws IllegalArgumentException
	 *             if the input stream contains a malformed Unicode escape
	 *             sequence.
	 * @throws IOException
	 *             if an I/O error occurs
	 * @see Properties#load(InputStream)
	 * @see Properties#load(java.io.Reader)
	 */
	public static Properties load(final Properties defaults, final File file)
			throws IOException {
		checkNotNull(file);
		final Properties properties = defaults == null ? new Properties()
				: new Properties(defaults);
		final Closer closer = Closer.create();
		final ByteSource source = Files.asByteSource(file);
		try {
			final InputStream in = closer.register(source.openBufferedStream());
			properties.load(in);
		} catch (Throwable e) {
			throw closer.rethrow(e, IllegalArgumentException.class);
		} finally {
			closer.close();
		}
		return properties;
	}

	/**
	 * Returns a new Java {@code Properties} object loaded from the specified
	 * XML document.
	 * <p>
	 * The XML document must have the following DOCTYPE declaration:
	 * 
	 * <pre>
	 * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
	 * </pre>
	 * 
	 * Furthermore, the document must satisfy the properties DTD described
	 * above.
	 * 
	 * @param file
	 *            the specified XML document
	 * @return a new Java {@code Properties} object loaded from the specified
	 *         file
	 * @throws InvalidPropertiesFormatException
	 *             if the specified file does not constitute a valid XML
	 *             document with the mandated document type
	 * @throws IOException
	 *             if an I/O error occurs
	 * @see Properties#loadFromXML(InputStream)
	 */
	public static Properties loadFromXML(final File file) throws IOException {
		checkNotNull(file);
		checkNotNull(file);
		return loadFromXML(null, file);
	}

	/**
	 * Returns a new Java {@code Properties} object loaded from the specified
	 * XML document, with optional defaults.
	 * <p>
	 * The XML document must have the following DOCTYPE declaration:
	 * 
	 * <pre>
	 * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
	 * </pre>
	 * 
	 * Furthermore, the document must satisfy the properties DTD described
	 * above.
	 * 
	 * @param defaults
	 *            the default properties or {@code null}
	 * @param file
	 *            the specified XML document
	 * @return a new Java {@code Properties} object loaded from the specified
	 *         file, with optional defaults
	 * @throws InvalidPropertiesFormatException
	 *             if the specified file does not constitute a valid XML
	 *             document with the mandated document type
	 * @throws IOException
	 *             if an I/O error occurs
	 * @see Properties#loadFromXML(InputStream)
	 */
	public static Properties loadFromXML(final Properties defaults,
			final File file) throws IOException {
		checkNotNull(file);
		checkNotNull(file);
		final Properties properties = defaults == null ? new Properties()
				: new Properties(defaults);
		final ByteSource source = Files.asByteSource(file);
		final Closer closer = Closer.create();
		try {
			final InputStream in = closer.register(source.openBufferedStream());
			properties.loadFromXML(in);
		} catch (Throwable e) {
			throw closer.rethrow(e, InvalidPropertiesFormatException.class);
		} finally {
			closer.close();
		}
		return properties;
	}

	/**
	 * Writes the given {@code Properties} to a file.
	 * <p>
	 * Properties from the defaults table of this Properties table (if any) are
	 * not written out by this method.
	 * 
	 * @param properties
	 *            the given {@code Properties}
	 * @param comments
	 *            a description of the property list, or {@code null} if no
	 *            comments are desired
	 * @param file
	 *            the file to write to
	 * @return the given file
	 * @see Properties#store(OutputStream, String)
	 * @throws IOException
	 *             if an I/O error occurs
	 */
	public static File save(final Properties properties, final String comments,
			final File file) throws IOException {
		checkNotNull(properties);
		checkNotNull(file);
		final ByteSink sink = Files.asByteSink(file);
		final Closer closer = Closer.create();
		try {
			final OutputStream out = sink.openBufferedStream();
			closer.register(out);
			properties.store(out, comments);
		} catch (Throwable e) {
			closer.rethrow(e);
		} finally {
			closer.close();
		}
		return file;
	}

	/**
	 * Writes an XML document representing the given {@code Properties} to a
	 * file, using the specified charset. Internally this methods delegates to
	 * {@link Properties#storeToXML(OutputStream, String, String)}.
	 * <p>
	 * The XML document will have the following DOCTYPE declaration:
	 * 
	 * <pre>
	 * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
	 * </pre>
	 * 
	 * @param properties
	 *            the given {@code Properties}
	 * @param comment
	 *            a description of the property list, or {@code null} if no
	 *            comment is desired
	 * @param file
	 *            the file to write to
	 * @param charset
	 *            the specified charset
	 * @return the given file
	 * @see Properties#storeToXML(OutputStream, String, String)
	 * @throws IOException
	 *             if an I/O error occurs
	 */
	public static File saveAsXML(final Properties properties,
			final String comment, final File file, final Charset charset)
			throws IOException {
		checkNotNull(properties);
		checkNotNull(file);
		checkNotNull(charset);
		final ByteSink sink = Files.asByteSink(file);
		final Closer closer = Closer.create();
		try {
			final OutputStream out = sink.openBufferedStream();
			closer.register(out);
			properties.storeToXML(out, comment, charset.toString());
		} catch (Throwable e) {
			closer.rethrow(e);
		} finally {
			closer.close();
		}
		return file;
	}

}
